/*
    This file is part of Sylvan.
    Copyright (C) 2008-2018 Cute Chess authors
    Copyright (C) 2019 Wilbert Lee

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sylvan.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <functional>
#include <algorithm>

#include <QSortFilterProxyModel>
#include <QFileDialog>
#include <QSettings>

#include <enginemanager.h>

#include "sylvanapp.h"
#include "engineconfigurationmodel.h"
#include "engineconfigurationdlg.h"
#include "enginemanagementwidget.h"
#include "ui_enginemanagementwidget.h"

EngineManagementWidget::EngineManagementWidget(QWidget* parent)
    : QWidget(parent),
      m_engineManager(SylvanApplication::instance()->engineManager()),
      m_hasChanged(false),
      m_filteredModel(new QSortFilterProxyModel(this)),
      ui(new Ui::EngineManagementWidget)
{
    ui->setupUi(this);

    // Set up a filtered model
    m_filteredModel->setSourceModel(new EngineConfigurationModel(
                                        m_engineManager, this));
    m_filteredModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
    m_filteredModel->setSortCaseSensitivity(Qt::CaseInsensitive);
    m_filteredModel->sort(0);
    m_filteredModel->setDynamicSortFilter(true);
    ui->m_enginesList->setModel(m_filteredModel);

    connect(ui->m_searchEngineEdit, SIGNAL(textChanged(QString)),
            this, SLOT(updateSearch(QString)));

    // Signals for updating the UI
    connect(ui->m_enginesList->selectionModel(),
            SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
            this, SLOT(updateUi()));
    connect(ui->m_enginesList, SIGNAL(doubleClicked(QModelIndex)),
            this, SLOT(configureEngine(QModelIndex)));

    // Add button
    connect(ui->m_addBtn, SIGNAL(clicked(bool)), this, SLOT(addEngine()));

    // Configure button
    connect(ui->m_configureBtn, SIGNAL(clicked(bool)), this,
            SLOT(configureEngine()));

    // Remove button
    connect(ui->m_removeBtn, SIGNAL(clicked(bool)), this, SLOT(removeEngine()));

    // Default location for new engines
    QString dir = QSettings().value("ui/default_engine_location").toString();
    ui->m_defaultLocationEdit->setText(dir);
    connect(ui->m_browseDefaultLocationBtn, SIGNAL(clicked()),
            this, SLOT(browseDefaultLocation()));

    updateEngineCount();
}

EngineManagementWidget::~EngineManagementWidget()
{
    delete ui;
}

bool EngineManagementWidget::hasConfigChanged() const
{
    return m_hasChanged;
}

void EngineManagementWidget::saveConfig()
{
    QString confPath = SylvanApplication::instance()->configPath();
    m_engineManager->saveEngines(confPath + QLatin1String("/engines.json"));
}

void EngineManagementWidget::updateUi()
{
    // Enable buttons depending on item selections
    ui->m_configureBtn->setEnabled(
                ui->m_enginesList->selectionModel()->hasSelection());

    ui->m_removeBtn->setEnabled(
                ui->m_enginesList->selectionModel()->hasSelection());
}

void EngineManagementWidget::updateSearch(const QString& terms)
{
    m_filteredModel->setFilterWildcard(terms);
    updateEngineCount();
}

void EngineManagementWidget::updateEngineCount()
{
    QString text = tr("Showing %1 of %2 engines")
            .arg(m_filteredModel->rowCount())
            .arg(m_engineManager->engineCount());
    ui->m_engineCountLabel->setText(text);
}

void EngineManagementWidget::addEngine()
{
    auto dlg = new EngineConfigurationDialog(
                EngineConfigurationDialog::AddEngine, this);
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->setReservedNames(m_engineManager->engineNames());

    connect(dlg, &EngineConfigurationDialog::accepted, [=]()
    {
        m_engineManager->addEngine(dlg->engineConfiguration());
        m_hasChanged = true;
        updateEngineCount();
    });
    dlg->open();
}

void EngineManagementWidget::configureEngine()
{
    configureEngine(ui->m_enginesList->currentIndex());
}

void EngineManagementWidget::configureEngine(const QModelIndex& index)
{
    // Map the index from the filtered model to the original model
    int row = m_filteredModel->mapToSource(index).row();
    const EngineConfiguration& config = m_engineManager->engineAt(row);

    QSet<QString> names = m_engineManager->engineNames();
    names.remove(config.name());

    auto dlg = new EngineConfigurationDialog(
                EngineConfigurationDialog::ConfigureEngine, this);
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->applyEngineInformation(config);
    dlg->setReservedNames(names);

    connect(dlg, &EngineConfigurationDialog::accepted, [=]()
    {
        m_engineManager->updateEngineAt(row, dlg->engineConfiguration());
        m_hasChanged = true;
    });
    dlg->open();
}

void EngineManagementWidget::removeEngine()
{
    const QItemSelection selection =
            m_filteredModel->mapSelectionToSource(ui->m_enginesList->selectionModel()->selection());
    QModelIndexList selected = selection.indexes();

    // Can't use std::greater because operator> isn't implemented
    // for QModelIndex.
    std::sort(selected.begin(), selected.end(),
              [](const QModelIndex &a, const QModelIndex &b)
    {
        return b < a;
    });

    if (!selected.isEmpty())
    {
        for (const QModelIndex& index : qAsConst(selected))
            m_engineManager->removeEngineAt(index.row());
        m_hasChanged = true;
        updateEngineCount();
    }
}

void EngineManagementWidget::browseDefaultLocation()
{
    auto dlg = new QFileDialog(
                this, tr("Choose Directory"),
                ui->m_defaultLocationEdit->text());
    dlg->setFileMode(QFileDialog::Directory);
    dlg->setOption(QFileDialog::ShowDirsOnly);
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    dlg->setAcceptMode(QFileDialog::AcceptOpen);

    connect(dlg, &QFileDialog::fileSelected, [=](const QString& dir)
    {
        ui->m_defaultLocationEdit->setText(dir);
        QSettings().setValue("ui/default_engine_location", dir);
    });
    dlg->open();
}
